package org.thinginitself.mixpath.jdom;

import org.jaxen.BaseXPath;
import org.jaxen.JaxenException;
import org.jaxen.Navigator;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.Parent;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.thinginitself.mixpath.MiXPathException;
import org.thinginitself.mixpath.MutableXPath;
import org.thinginitself.mixpath.Options;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author Andrew Harrison
 * @version 1.0.0 16/05/2011
 */
public class JDomMutablePath extends BaseXPath implements MutableXPath {

    public JDomMutablePath(String xpathExpr) throws JaxenException {
        super(xpathExpr, JDomMiXPathNavigator.getNavigator());
    }

    public JDomMutablePath(String xpathExpr, Navigator navigator) throws JaxenException {
        super(xpathExpr, navigator);
    }

    @Override
    public boolean insertElement(Object node, String name, List selection) throws MiXPathException {
        Element el = createElement(node, name, null, null);
        return insertElement(el, selection);
    }

    @Override
    public boolean insertElement(Object node, String name, String namespace, List selection) throws MiXPathException {
        Element el = createElement(node, name, namespace, null);
        return insertElement(el, selection);
    }

    @Override
    public boolean insertElement(Object node, String name, String namespace, String text, List selection) throws MiXPathException {
        Element el = createElement(node, name, namespace, text);
        return insertElement(el, selection);
    }


    public boolean insertText(Object node, String text, List selection) throws MiXPathException {
        try {

            if (selection == null || selection.size() == 0) {
                return false;
            }
            for (Object o : selection) {
                if (o instanceof Element) {
                    Element e = (Element) o;
                    e.setText(text);
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            }
            return true;

        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }

    private Element createElement(Object node, String name, String namespace, String text) {
        Element ret = null;
        if (namespace == null) {
            ret = new Element(name);
        } else {
            Namespace ns = null;

            if (node instanceof Element) {
                String pref = getPrefix((Element) node, namespace);
                ns = Namespace.getNamespace(pref, namespace);
            } else {
                ns = Namespace.getNamespace(namespace);
            }
            ret = new Element(name, ns);
        }
        if (text != null) {
            ret.setText(text);
        }
        return ret;
    }

    private boolean insertElement(Element insert, List selection) throws MiXPathException {
        try {
            if (insert == null) {
                throw new MiXPathException("Cannot add content. Unsupported content type: " + insert.getClass().getName());
            }

            if (selection == null || selection.size() == 0) {
                return false;
            }
            if (selection.size() == 1) {
                Object o = selection.get(0);
                if (o instanceof Element) {
                    Element e = (Element) o;
                    e.addContent(insert);
                } else if (o instanceof Document) {
                    Document doc = (Document) o;
                    doc.setRootElement(insert);
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            } else {
                for (Object o : selection) {
                    if (o instanceof Element) {
                        Element e = (Element) o;
                        e.addContent((Element) insert.clone());
                    } else {
                        throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                    }
                }
            }
            return true;
        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }


    public boolean insertAttribute(Object node, String name, String value, List selection) throws MiXPathException {
        return insertAttribute(node, name, null, value, selection);
    }


    public boolean insertAttribute(Object node, String name, String namespace, String value, List selection) throws MiXPathException {
        try {
            if (selection == null || selection.size() == 0) {
                return false;
            }
            for (Object o : selection) {
                if (o instanceof Element) {
                    Element e = (Element) o;
                    if (namespace == null) {
                        e.setAttribute(name, value);
                    } else {
                        e.setAttribute(name, value, Namespace.getNamespace(namespace));
                    }
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            }
            return true;

        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }

    public boolean removeAttribute(Object node, String name, List selection) throws MiXPathException {
        return removeAttribute(node, name, null, selection);
    }

    public boolean removeAttribute(Object node, String name, String namespace, List selection) throws MiXPathException {
        try {
            if (selection == null || selection.size() == 0) {
                return false;
            }
            for (Object o : selection) {
                if (o instanceof Element) {
                    Element e = (Element) o;
                    if (namespace == null) {
                        e.removeAttribute(name);
                    } else {
                        e.removeAttribute(name, Namespace.getNamespace(namespace));
                    }
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            }
            return true;
        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }

    public boolean removeElements(Object node, String name, List selection) throws MiXPathException {
        return removeElements(node, name, null, selection);
    }

    @Override
    public boolean removeElement(Object node) throws MiXPathException {
        Element c = null;
        if (node instanceof Element) {
            c = (Element) node;
        } else if (node instanceof Document) {
            c = ((Document) node).getRootElement();
        } else {
            throw new MiXPathException("Result of select is not an Element:" + node.getClass().getName());
        }
        Element parent = c.getParentElement();
        if (parent != null) {
            Element p = parent;
            p.removeContent(c);
            return true;
        } else {
            Parent par = c.getParent();
            if (par != null) {
                par.removeContent(c);
                return true;
            }
        }
        return false;
    }


    @Override
    public void writeTo(Object context, OutputStream stream, Options options) throws MiXPathException {

        XMLOutputter out = createOutputter(options);
        if (context instanceof Element) {
            try {
                out.output((Element) context, stream);
            } catch (IOException e) {
                throw new MiXPathException(e);
            }
        } else if (context instanceof Document) {
            try {
                out.output((Document) context, stream);
            } catch (IOException e) {
                throw new MiXPathException(e);
            }
        }
    }


    @Override
    public void writeTo(Object context, Writer stream, Options options) throws MiXPathException {

        XMLOutputter out = createOutputter(options);
        if (context instanceof Element) {
            try {
                out.output((Element) context, stream);
            } catch (IOException e) {
                throw new MiXPathException(e);
            }
        } else if (context instanceof Document) {
            try {
                out.output((Document) context, stream);
            } catch (IOException e) {
                throw new MiXPathException(e);
            }
        }

    }


    private XMLOutputter createOutputter(Options options) {
        XMLOutputter out = new XMLOutputter();
        Format f = Format.getRawFormat();
        if (options.isPretty()) {
            f = Format.getPrettyFormat();
        }
        f.setOmitDeclaration(!options.isEmitDeclaration());
        f.setOmitEncoding(!options.isEmitEncoding());
        f.setEncoding(options.getEncoding());
        out.setFormat(f);
        return out;
    }

    public boolean removeElements(Object node, String name, String namespace, List selection) throws MiXPathException {
        try {

            if (selection == null || selection.size() == 0) {
                return false;
            }
            for (Object o : selection) {
                List toRemove = new ArrayList();
                Element c = null;
                if (o instanceof Element) {
                    c = (Element) o;
                } else if (o instanceof Document) {
                    c = ((Document) o).getRootElement();
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }

                if (namespace == null) {
                    List childs = c.getChildren(name);
                    for (Object child : childs) {
                        if (child instanceof Element) {
                            toRemove.add(child);
                        }
                    }

                } else {
                    List childs = c.getChildren(name, Namespace.getNamespace(namespace));

                    for (Object child : childs) {
                        if (child instanceof Element) {
                            toRemove.add(child);
                        }
                    }
                }
                for (Object o1 : toRemove) {
                    c.removeContent((Element) o1);
                }

            }
            return true;
        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }


    private List createList(Object node, int index, boolean current) throws JaxenException {
        if (current) {
            return Arrays.asList(node);
        }
        if (index == 1) {
            return Arrays.asList(selectSingleNode(node));
        } else if (index < 1) {
            return selectNodes(node);
        } else {
            List l = selectNodes(node);
            if (l.size() < index) {
                // TODO: or throw an exception?
                return Collections.emptyList();
            } else {
                return Arrays.asList(l.get(index - 1));
            }
        }
    }


    private String getPrefix(Element node, String uri) {
        while (node != null) {
            if (node.getNamespaceURI().equals(uri)) {
                if (node.getNamespacePrefix() != null) {
                    return node.getNamespacePrefix();
                }
            }
            node = node.getParentElement();
        }
        return null;
    }
}
